/**
 * Copyright 2018 jianggujin (www.jianggujin.com).
 * 
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 * 
 *      http://www.apache.org/licenses/LICENSE-2.0
 * 
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package com.jianggujin.modulelink.magager;

import java.util.HashSet;
import java.util.Iterator;
import java.util.Map.Entry;
import java.util.Set;

import com.jianggujin.modulelink.config.JModuleLoadPolicy;
import com.jianggujin.modulelink.config.JMultiVersionModuleConfig;
import com.jianggujin.modulelink.exception.JModuleExistsException;
import com.jianggujin.modulelink.exception.JModuleLinkException;
import com.jianggujin.modulelink.exception.JModuleNotFoundException;
import com.jianggujin.modulelink.module.JModule;
import com.jianggujin.modulelink.module.JMultiVersionModule;
import com.jianggujin.modulelink.util.JAssert;
import com.jianggujin.modulelink.util.JLogFactory;
import com.jianggujin.modulelink.util.JLogFactory.JLog;
import com.jianggujin.modulelink.util.JModuleUtils;

/**
 * 多版本模块管理器默认实现
 * 
 * @author jianggujin
 *
 */
public class JDefaultMultiVersionModuleManager extends
        JAbstractModuleManager<JMultiVersionModule, JMultiVersionModuleConfig> implements JMultiVersionModuleManager {

    private static final JLog logger = JLogFactory.getLog(JDefaultMultiVersionModuleManager.class);
    private volatile static JDefaultMultiVersionModuleManager instance = null;

    @Override
    public JModule<JMultiVersionModuleConfig> load(JMultiVersionModuleConfig moduleConfig) {
        JAssert.checkNotNull(moduleConfig, "moduleConfig must not be null");
        return this.load(moduleConfig, moduleConfig.isActive());
    }

    @Override
    public synchronized JModule<JMultiVersionModuleConfig> load(JMultiVersionModuleConfig moduleConfig,
            boolean active) {
        JAssert.checkNotNull(moduleConfig, "moduleConfig must not be null");
        if (logger.isInfoEnabled()) {
            logger.info("Loading module: " + moduleConfig);
        }
        String moduleName = moduleConfig.getModuleName(), version = moduleConfig.getVersion();
        JModule<JMultiVersionModuleConfig> oldModule = null;
        JMultiVersionModule multiVersionModule = this.modules.get(moduleName);

        if (multiVersionModule == null) {
            multiVersionModule = new JMultiVersionModule(moduleName);
            this.modules.put(moduleName, multiVersionModule);
        } else {
            // 已经存在该版本
            oldModule = multiVersionModule.optModule(version);
            if (oldModule != null) {
                JModuleLoadPolicy moduleLoadPolicy = moduleConfig.getModuleLoadPolicy();
                switch (moduleLoadPolicy) {
                case AbortPolicy:
                    throw new JModuleExistsException("duplicated module, module name:{}, version:{}", moduleName,
                            version);
                case DiscardPolicy:
                    return null;
                default:
                    // DO NOTHING
                    break;
                }
            }
        }
        JModule<JMultiVersionModuleConfig> newModule = null;
        try {
            newModule = this.getModuleLoader().load(moduleConfig, this);
            JAssert.checkNotNull(newModule, "load module is null.");
        } catch (Throwable e) {
            if (multiVersionModule.isEmpty()) {
                this.modules.remove(moduleName);
            }
            throw new JModuleLinkException("load module error.", e);
        }
        multiVersionModule.addModule(newModule);
        // 销毁原模块
        JModuleUtils.destroyQuietly(oldModule);
        if (active) {
            newModule.active();
        } else {
            newModule.unActive();
        }
        return newModule;
    }

    @Override
    public synchronized void unload(String name) {
        JAssert.checkNotNull(name, "module name must not be null");
        JMultiVersionModule multiVersionModule = this.modules.remove(name);
        if (multiVersionModule == null) {
            throw new JModuleNotFoundException("could not found module with name:" + name);
        }
        multiVersionModule.destory();
    }

    @Override
    public synchronized void unload(String name, String version) {
        JAssert.checkNotNull(name, "module name must not be null");
        JAssert.checkNotNull(version, "module version must not be null");
        JMultiVersionModule multiVersionModule = this.modules.get(name);
        if (multiVersionModule == null) {
            throw new JModuleNotFoundException("could not found module with name:" + name);
        }
        boolean empty = multiVersionModule.destory(version);
        if (empty) {
            this.modules.remove(name);
        }
    }

    @Override
    public synchronized void unload() {
        Iterator<Entry<String, JMultiVersionModule>> iterator = this.modules.entrySet().iterator();
        while (iterator.hasNext()) {
            Entry<String, JMultiVersionModule> entry = iterator.next();
            entry.getValue().destory();
            iterator.remove();
        }
    }

    @Override
    public JModule<JMultiVersionModuleConfig> optModule(String moduleName) {
        JAssert.checkNotNull(moduleName, "module name must not be null");
        JMultiVersionModule multiVersionModule = this.modules.get(moduleName);
        if (multiVersionModule == null) {
            return null;
        }
        return multiVersionModule.optDefaultModule();
    }

    @Override
    public JModule<JMultiVersionModuleConfig> getModule(String moduleName, String version) {
        JModule<JMultiVersionModuleConfig> module = optModule(moduleName, version);
        if (module == null) {
            throw new JModuleNotFoundException(
                    "could not found module with module name:" + moduleName + " and version:" + version);
        }
        return module;
    }

    @Override
    public JModule<JMultiVersionModuleConfig> optModule(String moduleName, String version) {
        JAssert.checkNotNull(moduleName, "module name must not be null");
        JAssert.checkNotNull(version, "module version must not be null");
        JMultiVersionModule multiVersionModule = this.modules.get(moduleName);
        if (multiVersionModule == null) {
            return null;
        }
        return multiVersionModule.optModule(version);
    }

    @Override
    public synchronized void setDefaultVersion(String moduleName, String version) {
        JAssert.checkNotNull(moduleName, "module name must not be null");
        JAssert.checkNotNull(version, "module version must not be null");
        JMultiVersionModule multiVersionModule = this.modules.get(moduleName);
        if (multiVersionModule == null) {
            throw new JModuleNotFoundException("could not found module with module name:" + moduleName);
        }
        multiVersionModule.setDefaultVersion(version);
    }

    @Override
    public String getDefaultVersion(String moduleName) {
        String version = this.optDefaultVersion(moduleName);
        if (version == null) {
            throw new JModuleNotFoundException("could not found default module with module name:" + moduleName);
        }
        return version;
    }

    @Override
    public String optDefaultVersion(String moduleName) {
        JAssert.checkNotNull(moduleName, "module name must not be null");
        JMultiVersionModule multiVersionModule = this.modules.get(moduleName);
        if (multiVersionModule == null) {
            throw new JModuleNotFoundException("could not found module with module name:" + moduleName);
        }
        return multiVersionModule.optDefaultVersion();
    }

    @Override
    public boolean has(String moduleName, String version) {
        return moduleName != null && version != null && this.modules.containsKey(moduleName)
                && this.modules.get(moduleName).hasVersion(version);
    }

    @Override
    public Set<String> getModuleVersions(String moduleName) {
        Set<String> versions = optModuleVersions(moduleName);
        if (modules == null) {
            throw new JModuleNotFoundException("could not found module with module name:" + moduleName);
        }
        return versions;
    }

    @Override
    public Set<String> optModuleVersions(String moduleName) {
        JAssert.checkNotNull(moduleName, "module name must not be null");
        JMultiVersionModule multiVersionModule = this.modules.get(moduleName);
        if (multiVersionModule == null) {
            return null;
        }
        return multiVersionModule.getVersions();
    }

    @Override
    public Set<JModule<JMultiVersionModuleConfig>> getModules() {
        Set<JModule<JMultiVersionModuleConfig>> modules = new HashSet<JModule<JMultiVersionModuleConfig>>();
        for (JMultiVersionModule multiVersionModule : this.modules.values()) {
            modules.addAll(multiVersionModule.getModules());
        }
        return modules;
    }

    @Override
    public Set<JModule<JMultiVersionModuleConfig>> getModules(String moduleName) {
        Set<JModule<JMultiVersionModuleConfig>> modules = optModules(moduleName);
        if (modules == null) {
            throw new JModuleNotFoundException("could not found module with module name:" + moduleName);
        }
        return modules;
    }

    @Override
    public Set<JModule<JMultiVersionModuleConfig>> optModules(String moduleName) {
        JAssert.checkNotNull(moduleName, "module name must not be null");
        JMultiVersionModule multiVersionModule = this.modules.get(moduleName);
        if (multiVersionModule == null) {
            return null;
        }
        return new HashSet<JModule<JMultiVersionModuleConfig>>(multiVersionModule.getModules());
    }

    /**
     * 获得默认模块加载器
     * 
     * @return 模块加载器
     */
    public static JDefaultMultiVersionModuleManager getInstance() {
        if (instance == null) {
            synchronized (JDefaultMultiVersionModuleManager.class) {
                if (instance == null) {
                    instance = new JDefaultMultiVersionModuleManager();
                }
            }
        }
        return instance;
    }
}
